{
 "metadata": {
  "name": ""
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from __future__ import print_function, division\n",
      "import numpy as np\n",
      "\n",
      "# Create a big numpy array\n",
      "N = 1E7\n",
      "arr = np.random.randn(N)\n",
      "print(arr[:5])\n",
      "print('dtype=', arr.dtype)\n",
      "print('MBytes=', N*8 / 1E6)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "[ 0.1603029   0.36586917 -0.12065417  1.4464936  -2.65329948]\n",
        "dtype= float64\n",
        "MBytes= 80.0\n"
       ]
      }
     ],
     "prompt_number": 160
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "def clip(data):\n",
      "    return data.clip(0.4,0.5)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 161
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "def chunk_by_chunk_clip(data):\n",
      "    # n_chunks = 80 * 16 # 64KB fits into L1 = 16.2 ms per loop\n",
      "    # n_chunks = 80 * 4 # 256KB fits into L2 = 1.54 ms per loop\n",
      "    # n_chunks = 80 # 1MB = 0.367 ms\n",
      "    n_chunks = 40 # 2MB = 0.345 ms\n",
      "    # n_chunks = 90 // 3 # 3MB = 0.428 ms\n",
      "    chunk_len = N // n_chunks\n",
      "    # print('MBytes per chunk= {:.3f}MB'.format(chunk_len * 8 / 1E6))\n",
      "    boundaries = np.arange(0, N, chunk_len, dtype=np.uint)\n",
      "    for i in range(n_chunks-1):\n",
      "        chunk = data[boundaries[i]:boundaries[i+1]]\n",
      "        chunk = chunk.clip(0.4,0.5)\n",
      "    return data"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 173
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit clip(arr)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "10 loops, best of 3: 131 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 166
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit chunk_by_chunk_clip(arr)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "10 loops, best of 3: 83.1 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 174
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# OK, now let's try with pandas\n",
      "import pandas as pd\n",
      "\n",
      "series = pd.Series(arr)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 175
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit clip(series)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "1 loops, best of 3: 285 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 176
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit clip(series.values)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "10 loops, best of 3: 138 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 177
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit chunk_by_chunk_clip(series)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "1 loops, best of 3: 388 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 178
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit chunk_by_chunk_clip(series.values)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "10 loops, best of 3: 83.1 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 179
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# and let's try doing some pandas operations\n",
      "\n",
      "def rolling_mean(data):\n",
      "    return pd.rolling_mean(data, 30)\n",
      "\n",
      "def chunk_by_chunk_rolling_mean(data):\n",
      "    # n_chunks = 80 * 16 # 64KB fits into L1 = 16.2 ms per loop\n",
      "    # n_chunks = 80 * 4 # 256KB fits into L2 = 1.54 ms per loop\n",
      "    # n_chunks = 80 # 1MB = 0.367 ms\n",
      "    n_chunks = 40 # 2MB = 0.345 ms\n",
      "    # n_chunks = 90 // 3 # 3MB = 0.428 ms\n",
      "    chunk_len = N // n_chunks\n",
      "    # print('MBytes per chunk= {:.3f}MB'.format(chunk_len * 8 / 1E6))\n",
      "    boundaries = np.arange(0, N, chunk_len, dtype=np.uint)\n",
      "    for i in range(n_chunks-1):\n",
      "        chunk = data.iloc[boundaries[i]:boundaries[i+1]]\n",
      "        chunk = pd.rolling_mean(chunk, 30)\n",
      "    return data\n",
      "    "
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 180
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit rolling_mean(series)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "1 loops, best of 3: 237 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 181
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit chunk_by_chunk_rolling_mean(series)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "10 loops, best of 3: 171 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 182
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "series32 = series.astype(np.float32)\n",
      "%timeit chunk_by_chunk_rolling_mean(series32)\n",
      "# but if we double the chunk size then we get 5 ms per loop"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "1 loops, best of 3: 175 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 183
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Let's try multiple ops per chunk\n",
      "\n",
      "def rolling_mean_and_clip(data):\n",
      "    data = pd.rolling_mean(data, 30)\n",
      "    data = data.values.clip(0.4,0.5)\n",
      "    return data\n",
      "\n",
      "def chunk_by_chunk_rolling_mean_and_clip(data, n_chunks=40):\n",
      "    # n_chunks = 80 * 16 # 64KB fits into L1 = 16.2 ms per loop\n",
      "    # n_chunks = 80 * 4 # 256KB fits into L2 = 1.54 ms per loop\n",
      "    # n_chunks = 80 # 1MB = 0.367 ms\n",
      "    # n_chunks = 40 # 2MB = 0.345 ms\n",
      "    # n_chunks = 90 // 3 # 3MB = 0.428 ms\n",
      "    chunk_len = N // n_chunks\n",
      "    # print('MBytes per chunk= {:.3f}MB'.format(chunk_len * 8 / 1E6))\n",
      "    boundaries = np.arange(0, N, chunk_len, dtype=np.uint)\n",
      "    for i in range(n_chunks-1):\n",
      "        chunk = data.iloc[boundaries[i]:boundaries[i+1]]\n",
      "        chunk = pd.rolling_mean(chunk, 30)\n",
      "        chunk = chunk.values.clip(0.4,0.5)\n",
      "    return data"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 184
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit rolling_mean_and_clip(series)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "1 loops, best of 3: 346 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 185
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%timeit chunk_by_chunk_rolling_mean_and_clip(series)"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "1 loops, best of 3: 223 ms per loop\n"
       ]
      }
     ],
     "prompt_number": 186
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Create plots of chunk size against time\n",
      "\n",
      "import time\n",
      "step = 100 # kbytes\n",
      "max = 50000 # kbytes\n",
      "times = np.empty(max//step)\n",
      "for chunk_size in range(0,max,step)[1:]:\n",
      "    \"\"\"chunksize is in kBytes\"\"\"\n",
      "    chunk_len = (chunk_size * 1000) / 8\n",
      "    n_chunks = int(N // chunk_len)\n",
      "    if n_chunks == 0:\n",
      "        t0 = time.time()\n",
      "        rooling_mean_and_clip(series)\n",
      "    else:\n",
      "        t0 = time.time()\n",
      "        chunk_by_chunk_rolling_mean_and_clip(series, n_chunks)\n",
      "        \n",
      "    times[chunk_size//step] = time.time() - t0\n",
      "    \n",
      "times[0] = 0\n"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 202
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "plot(range(0,max,step)[1:], times[1:], label='chunked')\n",
      "hold('on')\n",
      "ylabel('time (miliseconds)')\n",
      "xlabel('chunk size (kBytes)')\n",
      "\n",
      "t0 = time.time()\n",
      "rolling_mean_and_clip(series)\n",
      "duration_for_unchunked = time.time() - t0\n",
      "plot(xlim(), [duration_for_unchunked, duration_for_unchunked], label='unchunked')\n",
      "legend()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "metadata": {},
       "output_type": "pyout",
       "prompt_number": 203,
       "text": [
        "<matplotlib.legend.Legend at 0x5bbba10>"
       ]
      },
      {
       "metadata": {},
       "output_type": "display_data",
       "png": "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\nZVz9rLsILQAAAABJRU5ErkJggg==\n",
       "text": [
        "<matplotlib.figure.Figure at 0x51d2dd0>"
       ]
      }
     ],
     "prompt_number": 203
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "## Conclusions\n",
      "\n",
      "Yes, it can be faster to process data in chunks (my L3 cache is 3MB).  I'm not entirely sure I trust these results though.  The bottom line is that I'm not going to bother getting data into small (cache-sized) chunks for now.  We can always add it later if we really want to.\n"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [],
     "language": "python",
     "metadata": {},
     "outputs": []
    }
   ],
   "metadata": {}
  }
 ]
}